package ebpf

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"oc_contributor_chenliangzhu/pkg/logging"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/perf"
	"github.com/cilium/ebpf/rlimit"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go bpf bpf/probe.c

// bpfFileOpenEvent is the data structure sent from the kernel.
// It must match the C struct file_open_event in probe.c.
type bpfFileOpenEvent struct {
	Filename [256]byte
}

// Tracer controls eBPF probes and data collection.
type Tracer struct {
	objs         bpfObjects
	links        []link.Link
	perfReader   *perf.Reader
	fileSet      map[string]struct{}
	stopChan     chan struct{}
	readFinished chan struct{}
}

// NewTracer creates a new eBPF tracer.
func NewTracer() (*Tracer, error) {
	if err := rlimit.RemoveMemlock(); err != nil {
		return nil, fmt.Errorf("failed to remove memlock rlimit: %w", err)
	}

	var objs bpfObjects
	if err := loadBpfObjects(&objs, nil); err != nil {
		return nil, fmt.Errorf("failed to load eBPF objects: %w", err)
	}

	return &Tracer{
		objs:         objs,
		fileSet:      make(map[string]struct{}),
		stopChan:     make(chan struct{}),
		readFinished: make(chan struct{}),
	}, nil
}

// Start starts the eBPF probes.
func (t *Tracer) Start() error {
	logging.Infof("Starting eBPF tracer...")
	tp, err := link.Tracepoint("syscalls", "sys_enter_openat", t.objs.TracepointSyscallsSysEnterOpenat, nil)
	if err != nil {
		return fmt.Errorf("failed to attach tracepoint: %w", err)
	}
	t.links = append(t.links, tp)

	// 默认的每CPU缓冲区大小为4KB，对于文件密集型操作可能不足
	// 我们将其增加到64KB来减少事件丢失的可能性
	const perCPUBufferSize = 64 * 1024 // 64KB
	t.perfReader, err = perf.NewReader(t.objs.Events, perCPUBufferSize)
	if err != nil {
		return fmt.Errorf("failed to create perf event reader: %w", err)
	}

	go t.readLoop()

	logging.Infof("eBPF tracer started successfully.")
	return nil
}

// Stop stops the eBPF probes and processes the collected data.
func (t *Tracer) Stop() error {
	logging.Infof("Stopping eBPF tracer...")
	close(t.stopChan)
	<-t.readFinished
	t.perfReader.Close()

	for _, l := range t.links {
		if err := l.Close(); err != nil {
			logging.Warnf("Error closing link: %v", err)
		}
	}
	t.objs.Close()
	logging.Infof("eBPF tracer stopped.")
	return nil
}

// GetCapturedFiles returns the list of captured file paths.
func (t *Tracer) GetCapturedFiles() []string {
	files := make([]string, 0, len(t.fileSet))
	for f := range t.fileSet {
		files = append(files, f)
	}
	return files
}

// readLoop is a goroutine that reads events from the perf buffer.
func (t *Tracer) readLoop() {
	defer close(t.readFinished)

	var event bpfFileOpenEvent
	for {
		select {
		case <-t.stopChan:
			return
		default:
			record, err := t.perfReader.Read()
			if err != nil {
				if errors.Is(err, perf.ErrClosed) {
					return
				}
				logging.Warnf("Error reading from perf buffer: %v", err)
				continue
			}

			if record.LostSamples > 0 {
				logging.Warnf("Lost %d samples from eBPF perf buffer", record.LostSamples)
				continue
			}

			if err := binary.Read(bytes.NewReader(record.RawSample), binary.LittleEndian, &event); err != nil {
				logging.Warnf("Error parsing eBPF event: %v", err)
				continue
			}

			filePath := string(event.Filename[:bytes.IndexByte(event.Filename[:], 0)])

			cwd, err := os.Getwd()
			if err != nil {
				logging.Warnf("Failed to get current working directory: %v", err)
				continue
			}
			absPath, err := filepath.Abs(filePath)
			if err != nil {
				logging.Warnf("Failed to get absolute path for %s: %v", filePath, err)
				continue
			}

			if filepath.HasPrefix(absPath, cwd) {
				if _, err := os.Stat(absPath); err == nil {
					t.fileSet[absPath] = struct{}{}
					logging.Debugf("eBPF captured file access: %s", absPath)
				}
			}
		}
	}
}
